
import 'dart:io';
import 'package:dio/dio.dart';
import 'package:dio/io.dart';
import 'package:xsdk_flutter/xsdk/operation/x_constant_temp.dart';
import 'package:xsdk_flutter/xsdk/utils/http/bean/x_http_request_cache.dart';
import 'package:xsdk_flutter/xsdk/utils/http/interceptor/XQueuedInterceptor.dart';
import 'package:xsdk_flutter/xsdk/utils/x_utils_date.dart';
import 'package:xsdk_flutter/xsdk/utils/json/x_utils_json.dart';
import 'package:xsdk_flutter/xsdk/utils/x_utils_file.dart';
import 'package:xsdk_flutter/xsdk/utils/x_utils_log.dart';
import 'package:xsdk_flutter/xsdk/utils/x_utils_number.dart';
import 'package:xsdk_flutter/xsdk/utils/x_utils_share_preferences.dart';
import 'package:xsdk_flutter/xsdk/utils/x_utils_string.dart';
import 'package:dio_smart_retry/dio_smart_retry.dart';


//成功响应
typedef XHttpCallbackSucceed = void Function(String result,Map<String,dynamic> headers_response);//headers_response: Map<String, List<String>>
//失败响应
typedef XHttpCallbackFailed = void Function(String code,String msg);

typedef XHttpDownloadCallback = void Function(bool isFinish,double progress);


/*
https://pub.flutter-io.cn/packages/dio/install
注意:编译成web应用时,浏览器有跨域问题,一般我们写一个网页的时候，网络请求可以通过后台服务器转发，由服务器解除跨域限制即可;

dependencies:
  dio: ^4.0.6
import 'package:dio/dio.dart';

其他:
http(更简洁方便)  https://pub-web.flutter-io.cn/packages/http


防网络抖动,自动重试2次
https://pub-web.flutter-io.cn/packages/dio_smart_retry
dependencies:
  dio_smart_retry: ^6.0.0
import 'package:dio_smart_retry/dio_smart_retry.dart';

 */

abstract class XHttpUtils{

  static final Dio _dio = Dio();

  static final Map<String,XHttpRequestCache?> _cacheMap={};

  //至少10分钟检查清除过期缓存(每次异步请求判断出发)
  static const int _checkCacheEachTime_ms=10*60*1000;
  //最近一次清除过期缓存时间
  static int _lastCleanCacheTime_ms=0;

  /*
  1.初始化,已在app中初始化
   */
  static void init(){
    /*
    设置超时时间等 ...
    BaseOptions 基类请求配置
    Options单次请求配置
    RequestOptions实际请求配置
     */

    //默认关闭自动重试(https://pub-web.flutter-io.cn/packages/dio_smart_retry)
    Map<String, dynamic> extra={};
    //等同于options.disableRetry=true;
    extra['ro_disable_retry']=true;

    _dio.options = BaseOptions(
      receiveTimeout: const Duration(seconds: 30),//适配测试环境
      connectTimeout: const Duration(seconds: 30),//5
      sendTimeout:const Duration(seconds: 60),//5
      extra:extra,
      //headers: getRequestHeaders()
    );

    //charles,fildder抓包工具的代理: 192.168.1.250:9000
    String proxy_ip_port=XSharePreferencesUtils.getString(XConstants.SP_proxy_ip_port);
    if(!XStringUtils.isEmpty(proxy_ip_port)){
      _dio.httpClientAdapter=IOHttpClientAdapter(createHttpClient:(){
        //
        HttpClient httpClient=HttpClient();
        //
        httpClient.findProxy=(uri){
          return 'PROXY $proxy_ip_port'; //
        };
        //代理工具会提供一个抓包的自签名证书,会通不过证书校验,所以需要禁用证书校验
        httpClient.badCertificateCallback=(X509Certificate cert, String host, int port){
          return true;
        };
        //
        return httpClient;
      });
    }


    // 添加拦截器，如 token之类，需要全局使用的参数
    XQueuedInterceptor xHttpInterceptor=XQueuedInterceptor('XQueuedInterceptor');
    //防止热重载调试时重复添加,造成多次重复请求
    if(!_dio.interceptors.contains(xHttpInterceptor)){
      _dio.interceptors.add(xHttpInterceptor);

      //防网络抖动,自动原地重试, 已在BaseOptions中设置默认关闭(需要服务端配合幂等处理,否则会造成数据重复写入)
      _dio.interceptors.add(
          RetryInterceptor(
            dio: _dio,
            logPrint: print, // specify log function (optional)
            retries: 2, // retry count (optional)
            retryDelays: const [ // set delays between retries (optional)
              Duration(milliseconds: 500), // wait 1 sec before first retry
              Duration(milliseconds: 500), // wait 2 sec before second retry
            ],
          )
      );

    }
    //dio.interceptors.add(XHttpInterceptor2());

    /*
    Transformer允许在向服务器发送/接收之前更改请求/响应数据。仅适用于请求方法“PUT”、“POST”和“PATCH”。
    Dio 已经实现了DefaultTransformer作为默认Transformer。
    如果您想自定义请求/响应数据的转换，您可以自己提供一个，并通过设置Transformer替换.DefaultTransformerdio.transformer
    如果你在 Flutter 开发中使用 dio，你最好使用 [compute] 函数在后台解码 json。
    //多线程json解析jsonDecodeCallback,已在自定义Transformer中设置
    //(dio.transformer as DefaultTransformer).jsonDecodeCallback = _parseJson;
     */
    //dio.transformer = XTransformer();

    //直接返回json字符串,不需要转换
    //dio.options.responseType=ResponseType.json;
    _dio.options.responseType=ResponseType.plain;
    //
    cleanCache();
  }

  //2.清除缓存(用于业务切换品牌,门店重新登录等操作时清除缓存)
  static void cleanCache(){
    _cacheMap.clear();
    _lastCleanCacheTime_ms=XDateUtils.getCurrentTime_ms();
  }

  //异步请求///////////////////////////////////
  /*
  业务api封装: 再次判断各个项目的特殊code返回值,其他api调用时再将map转成对应的response实体类
  static Map<String,dynamic>? _getHeaders(){
    return null;
  }

  static void requestApi_async(
      {
        required String url,
        required String requestJson,
        required RBApiCallbackSucceed succeed,
        required XHttpCallbackFailed failed,
      }
      ){

    XHttpUtils.request_async(
        url: url,
        headers: _getHeaders(),
        dataJson: requestJson,
        succeed: (String result,Map<String,dynamic> headers_response){
          RBBaseResponse response=RBBaseResponse.fromJson(XJsonUtils.json2Object(result));
          String? code = response.code;
          String? message = response.msg;
          dynamic ResultInt = response.ResultInt;
          String? ResultString = response.ResultString;

          if(response.code!=null){
            if(code=="200") {
              succeed(XJsonUtils.json2Object(result),headers_response);
            }else{
              failed(code??'-1',message??'request failed!');
            }

          }else{
            if(ResultInt!=null && '$ResultInt' == '0') {
              succeed(XJsonUtils.json2Object(result),headers_response);
            }else{
              failed('$ResultInt',ResultString??'request failed!');
            }
          }
        },
        failed:failed
    );

  }

  static Future<Map<String,dynamic>?> requestApi_sync(
      {
        required String url,
        required String requestJson,
      }
      ) async{

    String result=await XHttpUtils.request_sync(
        url: url,
        headers: _getHeaders(),
        dataJson: requestJson
    );

    if(XStringUtils.isEmpty(result)){
      return null;
    }

    RBBaseResponse response=RBBaseResponse.fromJson(XJsonUtils.json2Object(result));
    String? code = response.code;
    String? message = response.msg;
    dynamic ResultInt = response.ResultInt;
    String? ResultString = response.ResultString;

    if(response.code!=null){
      if(code=="200") {
        return XJsonUtils.json2Object(result);
      }else{
        return null;
      }
    }else{
      if(ResultInt!=null && '$ResultInt'== '0') {
        return XJsonUtils.json2Object(result);
      }else{
        return null;
      }
    }
  }

  //支付方式
  static void payList(
      {
        String? equipmentIdentity,
        required RBApiCallbackSucceedT<PayTypeResponse> succeed,
        required XHttpCallbackFailed failed,
      }
      ){
    //
    String url = "${getXServerUrl()}/app/Common/AppCode/PayType";
    //
    PayTypeRequest request=PayTypeRequest(equipmentIdentity:equipmentIdentity);
    //
    requestApi(
        url:url,
        requestJson:XJsonUtils.obj2Json(request),
        failed:failed,
        succeed: (Map<String,dynamic> responseMap,Map<String,dynamic> headers_response){
          PayTypeResponse response=PayTypeResponse.fromJson(responseMap);
          succeed(response,responseMap,headers_response);
        }
    );

  }
   */

  //注意:异步请求,不要在子线程中调用,否则无效还会阻塞执行
  static void request_async(
      {
        String requestType='post',//'get' 'post'
        Map<String,dynamic>? headers,
        required String url,
        dynamic dataJson,//可以是json字符串,也可以是 Map<String, dynamic>和FormData
        required XHttpCallbackSucceed succeed,
        required XHttpCallbackFailed failed,
        bool isUseCache=false,//是否使用缓存
        int cacheEffectiveTime_ms=1*60*1000,//缓存有效时间: 默认1分钟, 0则每次都先用上1次,再更新本地
        bool disableRetry=true,//默认关闭自动重试
        Options? options, //单次请求配置
        Duration? receiveTimeout,
      }
      )
  {

    //单次配置
    options ??= Options(
      receiveTimeout: receiveTimeout??const Duration(seconds: 30),
      sendTimeout:const Duration(seconds: 60),//5
      extra:_dio.options.extra,
    );

    //单次默认关闭自动重试
    options.disableRetry=disableRetry;

    //
    bool isHasEffectiveCache=false;//是否存在有效的缓存
    String cacheKey='$url$dataJson';
    if(isUseCache){
      //启用缓存
      XHttpRequestCache? itemCache=_cacheMap[cacheKey];
      if(itemCache!=null){
        int timeMs=itemCache.update_time_ms;
        String json=itemCache.json;
        Map<String,dynamic> header=itemCache.header;
        if(cacheEffectiveTime_ms<=0){
          //每次先返回上1次内容,然后再更新本地
          isHasEffectiveCache=true;
          //XLogUtils.printLog('---返回上1次缓存');
          succeed(json,header);
        }else{
          int currentTime=XDateUtils.getCurrentTime_ms();
          if(currentTime-timeMs>cacheEffectiveTime_ms){
            //超出有效时间,删除
            //XLogUtils.printLog('---超出有效时间,删除缓存');
            _cacheMap.remove(cacheKey);
          }else{
            //未超出有效时间
            //XLogUtils.printLog('---未超出有效时间,返回缓存');
            isHasEffectiveCache=true;
            succeed(json,header);
          }
        }
      }

      //触发清除过期缓存
      if(cacheEffectiveTime_ms>0
          && _cacheMap.isNotEmpty
          && (XDateUtils.getCurrentTime_ms()-_lastCleanCacheTime_ms>_checkCacheEachTime_ms)
      ){
        //
        if(XDateUtils.getCurrentTime_ms()-_lastCleanCacheTime_ms>_checkCacheEachTime_ms){
          _lastCleanCacheTime_ms=XDateUtils.getCurrentTime_ms();
          //开启线程检查过期缓存
          Future((){
            _cacheMap.removeWhere((String key, XHttpRequestCache? value){
              if(value==null){
                return true;
              }else{
                //最近更新时间
                int updateTimeMs=value.update_time_ms;
                //有效时间
                int settingEffectiveTimeMs=value.setting_effective_time_ms;
                if(settingEffectiveTimeMs>0
                    && (_lastCleanCacheTime_ms-updateTimeMs>settingEffectiveTimeMs)
                ){
                  return true;
                }
              }
              return false;
            });
          });
        }
      }

    }

    CancelToken _cancelToken = CancelToken();
    //
    if(cacheEffectiveTime_ms<=0 || !isHasEffectiveCache){
      try {
        //header
        _dio.options.headers=headers;

        //Response? response;
        if('GET'==requestType.toUpperCase()){

          //get请求
          Map<String, dynamic>? queryParameters;
          if(dataJson==null || dataJson is String){
            queryParameters=XJsonUtils.json2Map(dataJson);
          }else if(dataJson is Map<String, dynamic>){
            queryParameters=dataJson;
          }

          _dio.get(
            url,
            queryParameters: queryParameters,
            cancelToken: _cancelToken,
            options: options,
          ).then((Response response){
            if(200==response.statusCode){
              String result=response.data?.toString()??'';
              Headers headers=response.headers;

              if(isUseCache){
                //更新本地缓存
                _cacheMap[cacheKey]=XHttpRequestCache(
                  update_time_ms: XDateUtils.getCurrentTime_ms(),
                  setting_effective_time_ms: cacheEffectiveTime_ms,
                  json: result,
                  header: headers.map,
                );
                //
                if(isHasEffectiveCache){
                  //已返回缓存,不用再返回
                  return;
                }
              }

              //通知
              succeed(result,headers.map);

            }else{

              if(isUseCache && isHasEffectiveCache){
                //已返回缓存,不用再通知失败
                return;
              }

              failed('${response.statusCode??-1}',response.statusMessage??'');
            }
          }).catchError((e){

            if(isUseCache && isHasEffectiveCache){
              //已返回缓存,不用再通知失败
              return;
            }

            _customHandleError(e,failed);
          });
        }else{
          //post请求
          //response=await dio.post(url,data:json).catchError(onError);//等同于try catch
          _dio.post(
            url,
            data:dataJson,
            cancelToken: _cancelToken,
            options: options,
          ).then((Response response){
            if(200==response.statusCode){
              String result=response.data?.toString()??'';
              Headers headers=response.headers;

              if(isUseCache){
                //更新本地缓存
                _cacheMap[cacheKey]=XHttpRequestCache(
                    update_time_ms: XDateUtils.getCurrentTime_ms(),
                    setting_effective_time_ms: cacheEffectiveTime_ms,
                    json: result,
                    header: headers.map);
                XLogUtils.printLog('---更新请求缓存');
                //
                if(isHasEffectiveCache){
                  //已返回缓存,不用再返回
                  return;
                }
              }
              //
              succeed(result,headers.map);
            }else{
              if(isUseCache && isHasEffectiveCache){
                //已返回缓存,不用再通知失败
                return;
              }
              failed('${response.statusCode??-1}',response.statusMessage??'');
            }
          }).catchError((e,stacktrace){
            //这里有可能是其成功回调内部执行子逻辑时的报错
            XLogUtils.printLog('---请求报错: url=$url, $e');
            XLogUtils.printLog('$stacktrace');


            if(isUseCache && isHasEffectiveCache){
              //已返回缓存,不用再通知失败
              return;
            }

            //
            _customHandleError(e,failed);

          });
        }

      } catch (e) {

        if(isUseCache && isHasEffectiveCache){
          //已返回缓存,不用再通知失败
          return;
        }
        _customHandleError(e,failed);
      }
    }


  }


  static void _customHandleError(e,XHttpCallbackFailed failed){
    if(e is DioException){
      if(e.type==DioExceptionType.connectionTimeout
          ||e.type==DioExceptionType.sendTimeout
          ||e.type==DioExceptionType.receiveTimeout
      ){
        failed('-1', '网络连接超时,请检查网络');
        return;
      }else if(e.type==DioExceptionType.connectionError){
        failed('-1', '服务器连接失败,请稍后再试');
        return;
      }
    }
    failed('-1', '$e');
  }


  /*
  static Future<Map<String,dynamic>?> requestApi_sync(
      {
        required String url,
        required String requestJson,
      }
      ) async{

    String result=await XHttpUtils.request_sync(
        url: url,
        headers: _getHeaders(),
        dataJson: requestJson
    );

    if(XStringUtils.isEmpty(result)){
      return null;
    }

    RBBaseResponse response=RBBaseResponse.fromJson(XJsonUtils.json2Object(result));
    String? code = response.code;
    String? message = response.msg;
    int ResultInt = response.ResultInt;
    String? ResultString = response.ResultString;

    if(response.code!=null){
      if(code=="200") {
        return XJsonUtils.json2Object(result);
      }else{
        return null;
      }
    }else{
      if(ResultInt == 0) {
        return XJsonUtils.json2Object(result);
      }else{
        return null;
      }
    }
  }

static Future<Response_retry_pos_order?> retryPosOrder(
      {
        required List<String> fastOrderNos,
        required String groupId,
      }
      ) async{
    //
    String url = "${getXServerUrl2()}/retail-order-front/fast/order/task/push/retry/batch/pos";
    //
    Request_retry_pos_order request=Request_retry_pos_order();
    request.fastOrderNos=fastOrderNos;
    request.groupId=groupId;
    //
    Map<String,dynamic>? responseMap= await requestApi_sync(
        url:url,
        requestJson:XJsonUtils.obj2Json(request)
    );

    if(responseMap!=null){
      Response_retry_pos_order response=Response_retry_pos_order.fromJson(responseMap);
      return response;
    }

    return null;

  }

   */
  static Future<String> request_sync(
      {
        String requestType='post',//'get' 'post'
        Map<String,dynamic>? headers,
        required String url,
        dynamic dataJson,//可以是json字符串,也可以是 Map<String, dynamic>和FormData
        Options? options,
        Duration? receiveTimeout,
        bool disableRetry=true,
      }
      )
  async {

    try {
      //header
      _dio.options.headers=headers;

      //单次配置
      options ??= Options(
        receiveTimeout: receiveTimeout??const Duration(seconds: 30),
      );
      //单次默认关闭自动重试
      options.disableRetry=disableRetry;

      Response? response;
      CancelToken _cancelToken = CancelToken();

      //Response? response;
      if('GET'==requestType.toUpperCase()){
        //get请求
        Map<String, dynamic>? queryParameters;
        if(dataJson==null || dataJson is String){
          queryParameters=XJsonUtils.json2Map(dataJson);
        }else if(dataJson is Map<String, dynamic>){
          queryParameters=dataJson;
        }
        response = await _dio.get(
            url,
            options: options,
            queryParameters: queryParameters,
            cancelToken: _cancelToken
        );

      }else{
        //post请求
        //response=await dio.post(url,data:json).catchError(onError);//等同于try catch
        response = await _dio.post(
          url,
          data:dataJson,
          cancelToken: _cancelToken,
          options: options,
        );
      }

      XLogUtils.printLog('---statusCode=${response.statusCode??-1},statusMessage=${response.statusMessage}');

      if(200==response.statusCode){
        String result=response.data.toString();
        //Headers headers=response.headers;

        return result;
      }

    } catch (e) {
      XLogUtils.printLog('$e');
    }

    return '';
  }


//并发请求(同时多个):
// response= await Future.wait([dio.post("/info"),dio.get("/token")]);


  //下载文件
  static void download(
      {
        required String url,
        required String savePath,
        required XHttpDownloadCallback callback,
      }
      ){

    XFileUtils.deleteFile(savePath);

    _dio.download(
        url,
        savePath,
        onReceiveProgress:(int count, int total){
          //实测count有数据(0~18723654字节,文件大小),total=-1没有数据
          //print('---count=$count , total=$total');
          if(total>0){
            double progress= XNumberUtils.d2d(2, count/total);
            callback(false,progress);
          }else{
            callback(false,0.1);
          }

        }
    ).then((Response response){
      //print('---结束: ${response.toString()}');
      callback(true,1);
    });
  }


  static Future<Response> download_sync(
      {
        required String url,
        required String savePath,
      }
      ) {

    return _dio.download(
        url,
        savePath,
        onReceiveProgress:(int count, int total){
          //实测count有数据(0~18723654字节,文件大小),total=-1没有数据
          //print('---count=$count , total=$total');
          if(total>0){
            double progress= XNumberUtils.d2d(2, count/total);
            XLogUtils.printLog('---$url 下载进度: $progress');
          }
        }
    );
  }


  /*
  通过FormData上传多个文件:
  */
  static void uploadFiles(
      {
        required String url,
        Map<String,dynamic>? otherFromParams,
        required List<String> filePaths,
        required XHttpCallbackSucceed succeed,
        required XHttpCallbackFailed failed,
      }
      ) async{


    Map<String,dynamic> fromParams={};

    if(otherFromParams!=null && otherFromParams.isNotEmpty){
      fromParams.addAll(otherFromParams);
    }

    for(int i=0;i<filePaths.length;i++){
      String filePath=filePaths[i];
      if(i==0){
        fromParams['file']=await MultipartFile.fromFile(
          filePath,
        );
      }else{
        fromParams['file${i+1}']=await MultipartFile.fromFile(
          filePath,
        );
      }

    }

    FormData formData = FormData.fromMap(fromParams);

    // List<dynamic> files=[];
    // for(String path in filePaths){
    //   files.add(await MultipartFile.fromFile(
    //       path,
    //   )
    //   );
    // }
    // Map<String,dynamic> fromParams={
    //   'files':files,
    // };
    // FormData formData = FormData.fromMap(fromParams);


    _dio.post(url, data: formData).then((Response response){

      XLogUtils.printLog('----upload,response=${response.data}');

      if(200==response.statusCode){
        String result=response.data?.toString()??'';
        Headers headers=response.headers;

        //通知
        succeed(result,headers.map);

      }else{
        failed('${response.statusCode??-1}',response.statusMessage??'');
      }
    }).catchError((e){
      _customHandleError(e,failed);
    });

  }





}
